/*
 * Copyright (C) 2010 ARM Limited. All rights reserved.
 *
 * This program is free software and is provided to you under the terms of the GNU General Public License version 2
 * as published by the Free Software Foundation, and any use by you of this program is subject to the terms of such GNU licence.
 *
 * A copy of the licence is included with the program, and can also be obtained from Free Software
 * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301, USA.
 */

#include "mali_osk.h"
#include "mali_osk_list.h"
#include "ump_osk.h"
#include "ump_uk_types.h"

#include "ump_kernel_interface_ref_drv.h"
#include "ump_kernel_common.h"
#include "ump_kernel_descriptor_mapping.h"
#include <linux/rbtree.h>
#include <asm/memory.h>
#include <asm/pgtable.h>
#include <linux/sched.h>
#include <linux/mm.h>

#define UMP_MINIMUM_SIZE         4096
#define UMP_MINIMUM_SIZE_MASK    (~(UMP_MINIMUM_SIZE-1))
#define UMP_SIZE_ALIGN(x)        (((x)+UMP_MINIMUM_SIZE-1)&UMP_MINIMUM_SIZE_MASK)
#define UMP_ADDR_ALIGN_OFFSET(x) ((x)&(UMP_MINIMUM_SIZE-1))
static void phys_blocks_release(void * ctx, struct ump_dd_mem * descriptor);
bool rbnode_add_from_phys_block(ump_dd_mem * descriptor);
extern struct rb_root rb_root_struct;

UMP_KERNEL_API_EXPORT ump_dd_handle ump_dd_handle_create_from_phys_blocks(ump_dd_physical_block * blocks, unsigned long num_blocks)
{
	ump_dd_mem * mem;
	unsigned long size_total = 0;
	int map_id;
	u32 i;

	/* Go through the input blocks and verify that they are sane */
	for (i=0; i < num_blocks; i++)
	{
		unsigned long addr = blocks[i].addr;
		unsigned long size = blocks[i].size;

		DBG_MSG(5, ("Adding physical memory to new handle. Address: 0x%08lx, size: %lu\n", addr, size));
		size_total += blocks[i].size;

		if (0 != UMP_ADDR_ALIGN_OFFSET(addr))
		{
			MSG_ERR(("Trying to create UMP memory from unaligned physical address. Address: 0x%08lx\n", addr));
			return UMP_DD_HANDLE_INVALID;
		}

		if (0 != UMP_ADDR_ALIGN_OFFSET(size))
		{
			MSG_ERR(("Trying to create UMP memory with unaligned size. Size: %lu\n", size));
			return UMP_DD_HANDLE_INVALID;
		}
	}

	/* Allocate the ump_dd_mem struct for this allocation */
	mem = _mali_osk_malloc(sizeof(*mem));
	if (NULL == mem)
	{
		DBG_MSG(1, ("Could not allocate ump_dd_mem in ump_dd_handle_create_from_phys_blocks()\n"));
		return UMP_DD_HANDLE_INVALID;
	}

	/* Find a secure ID for this allocation */
	_mali_osk_lock_wait(device.secure_id_map_lock, _MALI_OSK_LOCKMODE_RW);
	map_id = ump_descriptor_mapping_allocate_mapping(device.secure_id_map, (void*) mem);

	if (map_id < 0)
	{
		_mali_osk_lock_signal(device.secure_id_map_lock, _MALI_OSK_LOCKMODE_RW);
		_mali_osk_free(mem);
		DBG_MSG(1, ("Failed to allocate secure ID in ump_dd_handle_create_from_phys_blocks()\n"));
		return UMP_DD_HANDLE_INVALID;
	}

	/* Now, make a copy of the block information supplied by the user */
	mem->block_array = _mali_osk_malloc(sizeof(ump_dd_physical_block)* num_blocks);
	if (NULL == mem->block_array)
	{
		ump_descriptor_mapping_free(device.secure_id_map, map_id);
		_mali_osk_lock_signal(device.secure_id_map_lock, _MALI_OSK_LOCKMODE_RW);
		_mali_osk_free(mem);
		DBG_MSG(1, ("Could not allocate a mem handle for function ump_dd_handle_create_from_phys_blocks().\n"));
		return UMP_DD_HANDLE_INVALID;
	}

	_mali_osk_memcpy(mem->block_array, blocks, sizeof(ump_dd_physical_block) * num_blocks);

	/* And setup the rest of the ump_dd_mem struct */
	_mali_osk_atomic_init(&mem->ref_count, 1);
	mem->secure_id = (ump_secure_id)map_id;
	mem->size_bytes = size_total;
	mem->nr_blocks = num_blocks;
	mem->backend_info = NULL;
	mem->ctx = NULL;
	mem->release_func = phys_blocks_release;
	/* For now UMP handles created by ump_dd_handle_create_from_phys_blocks() is forced to be Uncached */
	mem->is_cached = 0;

	/* Added ump_dd_mem into the RedBlack Tree */
	if(false == rbnode_add_from_phys_block(mem)) {
		DBG_MSG(1, ("%s Could not add a mem handle for function.\n", __FUNCTION__ ));
	}

	_mali_osk_lock_signal(device.secure_id_map_lock, _MALI_OSK_LOCKMODE_RW);
	DBG_MSG(3, ("UMP memory created. ID: %u, size: %lu\n", mem->secure_id, mem->size_bytes));

	return (ump_dd_handle)mem;
}

static void phys_blocks_release(void * ctx, struct ump_dd_mem * descriptor)
{
	_mali_osk_free(descriptor->block_array);
	descriptor->block_array = NULL;
}

ump_dd_handle search_sid(unsigned long phys)
{
	struct rb_node *n = rb_root_struct.rb_node;
	ump_dd_mem *rb_descriptor;

	while (n) {
		rb_descriptor = rb_entry(n, struct ump_dd_mem, rbnode);
		DBG_MSG(3, ("%s Searching descriptor(0x%08x).addr(0x%08x) == phys(0x%08x)\n", __FUNCTION__, 
					rb_descriptor, rb_descriptor->block_array[0].addr, phys));

		if (phys < rb_descriptor->block_array[0].addr)
			n = n->rb_left;
		else if (phys >= rb_descriptor->block_array[0].addr +
				rb_descriptor->block_array[0].size)
			n = n->rb_right;
		else{
			DBG_MSG(3, ("%s descriptor(%x): \n", __FUNCTION__, rb_descriptor));
			return (ump_dd_handle)rb_descriptor;
		}
	}
	return NULL;
}

bool rbnode_add_from_phys_block(ump_dd_mem * descriptor)
{
	struct rb_node **p = &rb_root_struct.rb_node;
	struct rb_node *parent = NULL;
	struct ump_dd_mem *rb_descriptor;
	int i;

	DEBUG_ASSERT_POINTER( descriptor );

	DBG_MSG(3, ("%s Adding descriptor(%x): \n", __FUNCTION__, descriptor));
	for (i = 0; i < sizeof(struct ump_dd_mem)/4; i++)
		DBG_MSG(6, ("%08x ", *((int *)(descriptor) + i)));

	while (*p) {
		parent = *p;
		rb_descriptor = rb_entry(parent, struct ump_dd_mem, rbnode);
		DBG_MSG(6, ("\ntmp_struct(%x), parent(%x) \n", rb_descriptor, parent));
		for (i = 0; i < sizeof(struct ump_dd_mem)/4; i++)
			DBG_MSG(6, (" %08x ", *((int *)(rb_descriptor) + i)));

		DBG_MSG(6, ("descriptor(%x)->block_array(%x)[0].addr(%x) ",
			descriptor, descriptor->block_array, descriptor->block_array[0].addr));

		if (descriptor->block_array[0].addr < rb_descriptor->block_array[0].addr )
		{
			p = &(*p)->rb_left;

			DBG_MSG(6, (" < rb_descriptor(%x)->block_array(%x)[0].addr(%x) \n",
			rb_descriptor, descriptor->block_array, descriptor->block_array[0].addr));

		}
		else if (descriptor->block_array[0].addr> rb_descriptor->block_array[0].addr)
		{
			p = &(*p)->rb_right;

			DBG_MSG(6, (" > rb_descriptor(%x)->block_array(%x)[0].addr(%x) \n",
			rb_descriptor, descriptor->block_array, descriptor->block_array[0].addr));
		}
		else
		{
			return false; /* found exiting one */
		}
	}

	rb_link_node(&descriptor->rbnode, parent, p);
	rb_insert_color(&descriptor->rbnode, &rb_root_struct);

	return true;
}

_mali_osk_errcode_t _ump_ukk_allocate( _ump_uk_allocate_s *user_interaction )
{
	ump_session_data * session_data = NULL;
	ump_dd_mem *new_allocation = NULL;
	ump_session_memory_list_element * session_memory_element = NULL;
	int map_id;

	DEBUG_ASSERT_POINTER( user_interaction );
	DEBUG_ASSERT_POINTER( user_interaction->ctx );

	session_data = (ump_session_data *) user_interaction->ctx;

	session_memory_element = _mali_osk_calloc( 1, sizeof(ump_session_memory_list_element));
	if (NULL == session_memory_element)
	{
		DBG_MSG(1, ("Failed to allocate ump_session_memory_list_element in ump_ioctl_allocate()\n"));
		return _MALI_OSK_ERR_NOMEM;
	}


	new_allocation = _mali_osk_calloc( 1, sizeof(ump_dd_mem));
	if (NULL==new_allocation)
	{
		_mali_osk_free(session_memory_element);
		DBG_MSG(1, ("Failed to allocate ump_dd_mem in _ump_ukk_allocate()\n"));
		return _MALI_OSK_ERR_NOMEM;
	}

	/* Create a secure ID for this allocation */
	_mali_osk_lock_wait(device.secure_id_map_lock, _MALI_OSK_LOCKMODE_RW);
	map_id = ump_descriptor_mapping_allocate_mapping(device.secure_id_map, (void*)new_allocation);

	if (map_id < 0)
	{
		_mali_osk_lock_signal(device.secure_id_map_lock, _MALI_OSK_LOCKMODE_RW);
		_mali_osk_free(session_memory_element);
		_mali_osk_free(new_allocation);
		DBG_MSG(1, ("Failed to allocate secure ID in ump_ioctl_allocate()\n"));
		return - _MALI_OSK_ERR_INVALID_FUNC;
	}

	/* Initialize the part of the new_allocation that we know so for */
	new_allocation->secure_id = (ump_secure_id)map_id;
	_mali_osk_atomic_init(&new_allocation->ref_count,1);
	if ( 0==(UMP_REF_DRV_UK_CONSTRAINT_USE_CACHE & user_interaction->constraints) )
		 new_allocation->is_cached = 0;
	else new_allocation->is_cached = 1;

	new_allocation->backend_info = (void*)user_interaction->constraints;

	/* special case a size of 0, we should try to emulate what malloc does in this case, which is to return a valid pointer that must be freed, but can't be dereferences */
	if (0 == user_interaction->size)
	{
		user_interaction->size = 1; /* emulate by actually allocating the minimum block size */
	}

	new_allocation->size_bytes = UMP_SIZE_ALIGN(user_interaction->size); /* Page align the size */

	/* Now, ask the active memory backend to do the actual memory allocation */
	if (!device.backend->allocate( device.backend->ctx, new_allocation ) )
	{
		DBG_MSG(3, ("OOM: No more UMP memory left. Failed to allocate memory in ump_ioctl_allocate(). Size: %lu, requested size: %lu\n", new_allocation->size_bytes, (unsigned long)user_interaction->size));
		ump_descriptor_mapping_free(device.secure_id_map, map_id);
		_mali_osk_lock_signal(device.secure_id_map_lock, _MALI_OSK_LOCKMODE_RW);
		_mali_osk_free(new_allocation);
		_mali_osk_free(session_memory_element);
		return _MALI_OSK_ERR_INVALID_FUNC;
	}

	new_allocation->ctx = device.backend->ctx;
	new_allocation->release_func = device.backend->release;

	/* Added ump_dd_mem into the RedBlack Tree */
	if(false == rbnode_add_from_phys_block(new_allocation)) {
		DBG_MSG(1, ("%s Could not add a mem handle for function.\n", __FUNCTION__ ));
	}

	/* Initialize the session_memory_element, and add it to the session object */
	session_memory_element->mem = new_allocation;
	_mali_osk_lock_wait(session_data->lock, _MALI_OSK_LOCKMODE_RW);
	_mali_osk_list_add(&(session_memory_element->list), &(session_data->list_head_session_memory_list));
	_mali_osk_lock_signal(session_data->lock, _MALI_OSK_LOCKMODE_RW);

	user_interaction->secure_id = new_allocation->secure_id;
	user_interaction->size = new_allocation->size_bytes;
	DBG_MSG(3, ("UMP memory allocated. ID: %u, size: %lu\n", new_allocation->secure_id, new_allocation->size_bytes));

	_mali_osk_lock_signal(device.secure_id_map_lock, _MALI_OSK_LOCKMODE_RW);

	return _MALI_OSK_ERR_OK;
}

UMP_KERNEL_API_EXPORT ump_dd_status_code ump_dd_meminfo_set(ump_dd_handle memh, void* args)
{
	ump_dd_mem * mem;
	ump_secure_id secure_id;

	DEBUG_ASSERT_POINTER(memh);

	secure_id = ump_dd_secure_id_get(memh);

	_mali_osk_lock_wait(device.secure_id_map_lock, _MALI_OSK_LOCKMODE_RW);
	if (0 == ump_descriptor_mapping_get(device.secure_id_map, (int)secure_id, (void**)&mem))
	{
		device.backend->set(mem, args);
	}
	else
	{
		_mali_osk_lock_signal(device.secure_id_map_lock, _MALI_OSK_LOCKMODE_RW);
		DBG_MSG(1, ("Failed to look up mapping in ump_meminfo_set(). ID: %u\n", (ump_secure_id)secure_id));
		return UMP_DD_INVALID;
	}

	_mali_osk_lock_signal(device.secure_id_map_lock, _MALI_OSK_LOCKMODE_RW);

	return UMP_DD_SUCCESS;
}

UMP_KERNEL_API_EXPORT void *ump_dd_meminfo_get(ump_secure_id secure_id, void* args)
{
	ump_dd_mem * mem;
	void *result;

	_mali_osk_lock_wait(device.secure_id_map_lock, _MALI_OSK_LOCKMODE_RW);
	if (0 == ump_descriptor_mapping_get(device.secure_id_map, (int)secure_id, (void**)&mem))
	{
		result = device.backend->get(mem, args);
	}
	else
	{
		_mali_osk_lock_signal(device.secure_id_map_lock, _MALI_OSK_LOCKMODE_RW);
		DBG_MSG(1, ("Failed to look up mapping in ump_meminfo_get(). ID: %u\n", (ump_secure_id)secure_id));
		return UMP_DD_HANDLE_INVALID;
	}

	_mali_osk_lock_signal(device.secure_id_map_lock, _MALI_OSK_LOCKMODE_RW);

	return result;
}

UMP_KERNEL_API_EXPORT int ump_dd_secure_id_get_from_vaddr(unsigned long vaddr, ump_secure_id *sid)
{
	ump_dd_mem * mem;
	unsigned long phys = 0;
	pgd_t *pgd;
	struct vm_area_struct *vma;

	BUG_ON( vaddr & ~PAGE_MASK );
	BUG_ON(!sid);

	vma = find_vma(current->mm, vaddr);
	if (!vma || (vma->vm_end <= vaddr)) {
		DBG_MSG(1, ("Virtual address 0x%08x is not allocated.\n", vaddr));
		return -EFAULT;
	}

	DBG_MSG(5, ("Getting handle from Virtual address. vaddr: 0x%08x\n", vaddr));

	pgd = pgd_offset(current->mm, vaddr);
	if (!pgd_none(*pgd)) {
		pud_t *pud = pud_offset(pgd, vaddr);
		if (!pud_none(*pud)) {
			pmd_t *pmd = pmd_offset(pud, vaddr);
			if (!pmd_none(*pmd)) {
				pte_t *pte = pte_offset_map(pmd, vaddr);
				if (pte_present(*pte))
					phys = pte_pfn(*pte) << PAGE_SHIFT;
				pte_unmap(pte);
			}
		}
	}

	if (phys == 0) {
		DBG_MSG(1, ("No mapping exists for virtual address 0x%08x.\n", vaddr));
		return -EFAULT;
	}

	mem = (ump_dd_mem*)search_sid(phys);
	pte_unmap(pte);

	if (!mem) {
		DBG_MSG(1, ("Failed to find secure ID from virtual address: 0x%08x\n", vaddr));
		return -ENOENT;
	}

	DBG_MSG(1, ("Getting handle's Handle : 0x%8lx secure_id: %d\n", mem, mem->secure_id));
	*sid = mem->secure_id;
	return 0;
}

_mali_osk_errcode_t _ump_ukk_secure_id_get_from_vaddr(_ump_uk_vaddr_s *user_interaction)
{
	ump_secure_id sid;

	DEBUG_ASSERT_POINTER( user_interaction );

	if (ump_dd_secure_id_get_from_vaddr(user_interaction->vaddr, &sid)) 
		return _MALI_OSK_ERR_ITEM_NOT_FOUND;

	user_interaction->secure_id = sid;

	return _MALI_OSK_ERR_OK;
}

